/*
 * Copyright (C) 2011 Dirk Weißenborn, dirk.weissenborn@gmail.com
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package com.weissenborn.colabtex.server;

import java.util.Date;
import java.util.List;
import java.util.Random;
import java.util.Timer;
import java.util.TimerTask;

import org.apache.commons.mail.EmailException;
import org.apache.shiro.SecurityUtils;
import org.apache.shiro.UnavailableSecurityManagerException;
import org.apache.shiro.authc.AuthenticationException;
import org.apache.shiro.authc.UsernamePasswordToken;
import org.apache.shiro.config.IniSecurityManagerFactory;
import org.apache.shiro.crypto.hash.Sha256Hash;
import org.apache.shiro.subject.Subject;
import org.apache.shiro.util.Factory;
import org.hibernate.Query;
import org.hibernate.Session;
import org.hibernate.Transaction;
import org.hibernate.criterion.Restrictions;

import com.weissenborn.colabtex.client.service.UserManagementService;
import com.weissenborn.colabtex.server.database.HibernateUtil;
import com.weissenborn.colabtex.server.helpers.MailHelper;
import com.weissenborn.colabtex.server.user.ColabTexUser;
import com.weissenborn.colabtex.server.user.Permission;
import com.google.gwt.user.server.rpc.RemoteServiceServlet;

/**
 * The Class UserManagementServiceImpl handles async requests from the client regarding the user management.
 */
public class UserManagementServiceImpl extends RemoteServiceServlet implements UserManagementService {

	/* (non-Javadoc)
	 * @see com.weissenborn.colabtex.client.service.UserManagementService#login(java.lang.String, java.lang.String)
	 */
	public String login(String userId, String password) {
		//prevent sql injection
		if(userId.contains(" ")||password.contains(" "))
			return null;
		
		Subject user = new Subject.Builder().buildSubject();
		UsernamePasswordToken token = new UsernamePasswordToken(userId,password);
        token.setRememberMe(true);
		
		try {
			user.login(token);
			user.getSession(true);
		} catch ( AuthenticationException ae ) {
			return null;
		}			
		
		if(user.isAuthenticated()) {		
			Session session = HibernateUtil.getSessionFactory().openSession();
			Transaction t = session.beginTransaction();
			
			ColabTexUser cUser = (ColabTexUser) session.createCriteria(ColabTexUser.class).
			           add(Restrictions.eq("name", userId)).uniqueResult();
			
			cUser.setLastLogin(new Date().getTime());
			
			session.update(cUser);
			t.commit();
			session.close();

		    return (String) user.getSession().getId();
		}
		return null;		
	}
	
	/* (non-Javadoc)
	 * @see com.weissenborn.colabtex.client.service.UserManagementService#initShiro()
	 */
	public void initShiro() {
		try {
			SecurityUtils.getSecurityManager();
		}
		catch(UnavailableSecurityManagerException ex){		
			Factory<org.apache.shiro.mgt.SecurityManager> factory = new IniSecurityManagerFactory(getClass().getClassLoader().getResource("com/weissenborn/colabtex/shiro.ini").getPath());		

			org.apache.shiro.mgt.SecurityManager securityManager = factory.getInstance();			
			SecurityUtils.setSecurityManager(securityManager);			
				
		}
	}

	/* (non-Javadoc)
	 * @see com.weissenborn.colabtex.client.service.UserManagementService#getUser(java.lang.String)
	 */
	public String getUser(String sessionID) {
		
		Subject user = new Subject.Builder().sessionId(sessionID).buildSubject();
		if(user.isAuthenticated())
			return (String) user.getPrincipal();
		
		return null;
	}

	/* (non-Javadoc)
	 * @see com.weissenborn.colabtex.client.service.UserManagementService#logout(java.lang.String)
	 */
	public void logout(String sessionID) {
		Subject user = new Subject.Builder().sessionId(sessionID).buildSubject();
		
		user.logout();
	}


	/* (non-Javadoc)
	 * @see com.weissenborn.colabtex.client.service.UserManagementService#deleteUser(java.lang.String, java.lang.String)
	 */
	public void deleteUser(String id, String password) {
		//prevent sql injection
		if(id.contains(" ")||password.contains(" "))
					return;
				
		Session session = HibernateUtil.getSessionFactory().openSession();
		Transaction t = session.beginTransaction();
		
		Sha256Hash hash = new Sha256Hash(password);
		
		ColabTexUser user = (ColabTexUser) session.createCriteria(ColabTexUser.class).
		           add(Restrictions.eq("name", id)).add(Restrictions.eq("password", hash.toHex())).uniqueResult();
		if(user==null) {
			session.close();
			return;
		}	
		
		String groupName ="";
		if(user.getPermissions()!=null)
			for (Permission p : user.getPermissions()) {
				groupName = p.getGroupName();
				if(p.getRole().equals(Permission.Role.admin)&&checkOnlyAdmin(groupName, session)) {
					Query q1 = session.createQuery("DELETE FROM PERMISSIONS WHERE groupname='"+groupName+"'");
					Query q2 = session.createQuery("DELETE FROM PERMISSION WHERE groupname='"+groupName+"'");
					Query q3 = session.createQuery("DELETE FROM IDNAMEMAPPING WHERE groupname='"+groupName+"'");
					
					q1.executeUpdate();
					q2.executeUpdate();
					q3.executeUpdate();					
				}
			}
		
		session.delete(user);
		
		t.commit();
		session.close();
	}
	
	/**
	 * Check only admin.
	 *
	 * @param groupName the group name
	 * @param session the session
	 * @return the boolean
	 */
	private Boolean checkOnlyAdmin(String groupName, Session session) {
		Query memberQuery = session.createSQLQuery("select name from Permissions where groupname='"+groupName+"' and role='admin'");
		List list = memberQuery.list();
		if(list.size()<2)
			return true;
		
		return false;
	}
	
	/* (non-Javadoc)
	 * @see com.weissenborn.colabtex.client.service.UserManagementService#register(java.lang.String)
	 */
	public Boolean register(final String email) {
		//prevent sql injection
		if(email.contains(" "))
					return false;

		Session session = HibernateUtil.getSessionFactory().openSession();
		Transaction t = session.beginTransaction();
		
		ColabTexUser user = (ColabTexUser) session.createCriteria(ColabTexUser.class).
					           add(Restrictions.eq("name", email)).uniqueResult();
		
		if(user != null) {
			t.commit();
			session.close();
			return false;
		}
				
		String password = generatePassword();
		
		try {			
			String text = "password: "+ password +"\n\n You have 1 hour for your first login, after that your login will expire!";
			MailHelper mailHelper = new MailHelper("com/weissenborn/colabtex/mail.ini", text);
			if(!mailHelper.sendMail(email))
				return false;
		} catch (EmailException e) {
			e.printStackTrace();
			return false;
		}
		
		Sha256Hash hash = new Sha256Hash(password);
		session.save(new ColabTexUser(email, hash.toHex()));
		t.commit();
		session.close();	
		
		Timer timer = new Timer();
		
		TimerTask tt = new TimerTask() {			
			@Override
			public void run() {
				Session session = HibernateUtil.getSessionFactory().openSession();
				Transaction t = session.beginTransaction();
				ColabTexUser user = (ColabTexUser) session.createCriteria(ColabTexUser.class).
				           add(Restrictions.eq("name", email)).uniqueResult();		
				
				if(user.getLastLogin()==null)
					session.delete(user);
				
				t.commit();
				session.close();
			}
		};
		
		timer.schedule(tt, 3600000);
		return true;		
	}

	/* (non-Javadoc)
	 * @see com.weissenborn.colabtex.client.service.UserManagementService#resetPassword(java.lang.String)
	 */
	public Boolean resetPassword(String email) {
		//prevent sql injection
		if(email.contains(" "))
				return false;
		
		Session session = HibernateUtil.getSessionFactory().openSession();
		Transaction t = session.beginTransaction();
		
		ColabTexUser user = (ColabTexUser) session.createCriteria(ColabTexUser.class).
		           add(Restrictions.eq("name", email)).uniqueResult();

		if(user == null) {
			t.commit();
			session.close();
			return false;
		}
			
		String password = generatePassword();
		
		try {
			String text = "password reseted to: "+ password;
			MailHelper mailHelper = new MailHelper("com/weissenborn/colabtex/mail.ini", text);			
		if(!mailHelper.sendMail(email))
			return false;
		} catch (EmailException e) {
			return false;
		}
			
		Sha256Hash hash = new Sha256Hash(password);
		user.setPassword(hash.toHex());
		session.update(user);
		t.commit();
		session.close();
		
		return true;
	}
	
	/**
	 * Generate password.
	 *
	 * @return the string
	 */
	public String generatePassword() {
		String password = "";
		Random rand = new Random();
		Integer next;
		for (int i = 0; i < 6; i++) {
			next = rand.nextInt(62);
			if(next>9)
				next += 7;
			if(next>42)
				next+=6;
			password += (char)	(next + 48);		
		}
		
		return password;
	}

	
	/* (non-Javadoc)
	 * @see com.weissenborn.colabtex.client.service.UserManagementService#setNewEmail(java.lang.String, java.lang.String, java.lang.String)
	 */
	public Boolean setNewEmail(String oldAdress, String newAdress, String password) {	
		//prevent sql injection
		if(oldAdress.contains(" ")||password.contains(" ")||newAdress.contains(" "))
			return null;
		Session session = HibernateUtil.getSessionFactory().openSession();
		
		Sha256Hash hash = new Sha256Hash(password);
		
		ColabTexUser user = (ColabTexUser) session.createCriteria(ColabTexUser.class).
		           add(Restrictions.eq("name", oldAdress)).add(Restrictions.eq("password", hash.toHex())).uniqueResult();
		
		session.close();
		if(user==null) {
			return false;
		}	
		
		if(!this.register(newAdress))
			return false;
		
		session = HibernateUtil.getSessionFactory().openSession();
		Transaction t = session.beginTransaction();
		
		user = (ColabTexUser) session.createCriteria(ColabTexUser.class).
		           add(Restrictions.eq("name", oldAdress)).uniqueResult();
		
		ColabTexUser userNew = (ColabTexUser) session.createCriteria(ColabTexUser.class).
		           add(Restrictions.eq("name", newAdress)).uniqueResult();
		
		//Update permissions
		if(user.getPermissions()!=null)
			for (Permission p : user.getPermissions()) {
				userNew.getPermissions().add(new Permission(p.getGroupName(), p.getRole()));					
			}	
		
		session.update(userNew);
		
		t.commit();
		session.close();
		
		this.deleteUser(oldAdress, password);
		return true;
	}

	/* (non-Javadoc)
	 * @see com.weissenborn.colabtex.client.service.UserManagementService#setNewPassword(java.lang.String, java.lang.String, java.lang.String)
	 */
	public Boolean setNewPassword(String id, String password, String newPassword) {
		//prevent sql injection
		if(id.contains(" ")||password.contains(" ")||newPassword.contains(" "))
			return null;
		
		Session session = HibernateUtil.getSessionFactory().openSession();
		Transaction t = session.beginTransaction();
		Sha256Hash hash = new Sha256Hash(password);
		
		ColabTexUser user = (ColabTexUser) session.createCriteria(ColabTexUser.class).
		           add(Restrictions.eq("name", id)).add(Restrictions.eq("password", hash.toHex())).uniqueResult();

		if(user == null) {
			t.commit();
			session.close();
			return false;
		}
			
		hash = new Sha256Hash(newPassword);
		user.setPassword(hash.toHex());
		session.update(user);
		t.commit();
		session.close();
		
		return true;
	}
}
